Skip to content

Conversation

@adnaan
Copy link
Contributor

@adnaan adnaan commented Jan 25, 2026

Summary

  • Modified parseValue() to check Number.MAX_SAFE_INTEGER
  • Large integers (like UnixNano timestamps) are kept as strings to preserve precision
  • Fixes an issue where IDs generated from time.Now().UnixNano() lost precision when converted to JavaScript numbers

Problem

JavaScript's Number.MAX_SAFE_INTEGER is 2^53-1 (9007199254740991). UnixNano timestamps exceed this limit, causing precision loss when the client's parseValue() function converted them to numbers.

For example:

  • Server stored: 1769358878696557000
  • Client sent: 1769358878696557056 (different due to float64 precision loss!)

Solution

The parseValue() function now checks if an integer exceeds Number.MAX_SAFE_INTEGER and keeps it as a string, allowing the server-side GetStringOk() to handle it correctly.

Test plan

  • E2E tests pass with large integer IDs
  • Toggle and delete operations work correctly

🤖 Generated with Claude Code

JavaScript's Number.MAX_SAFE_INTEGER is 2^53-1 (9007199254740991).
UnixNano timestamps exceed this, causing precision loss when converted
to float64. This fix keeps large integers as strings, allowing the
server-side GetStringOk to handle them correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 25, 2026

Review

Approved

The fix correctly addresses JavaScript's floating-point precision limitation for large integers (>2^53-1).

Code Quality

  • Logic is sound: checks if value is an integer and exceeds Number.MAX_SAFE_INTEGER
  • Good use of Number.isInteger() to ensure only integers are affected
  • Preserves backward compatibility for normal-sized numbers

Minor Observation

The condition value.trim() === num.toString() on line 520 may not catch edge cases where large integers already fail the equality check due to precision loss during parsing. However, the current implementation should work fine for the UnixNano timestamp use case since the MAX_SAFE_INTEGER check happens after confirming round-trip equality.

No blocking issues found. The fix is targeted and solves the precision loss problem effectively.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the client-side value parsing to avoid precision loss when handling large integer strings (e.g., UnixNano IDs) by preserving them as strings instead of converting to JavaScript numbers.

Changes:

  • Updated parseValue() to only coerce numeric strings to number when doing so won’t lose integer precision.
  • Added a safe-integer-range guard (Number.MAX_SAFE_INTEGER) for integer parsing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 518 to 525
// Try to parse as number, but only if it's safe (won't lose precision)
const num = parseFloat(value);
if (!isNaN(num) && value.trim() === num.toString()) {
// Check if the number is within JavaScript's safe integer range
// Large integers (like UnixNano timestamps) lose precision as float64
if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {
// Keep as string to preserve precision
return value;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the numeric string is outside MAX_SAFE_INTEGER, this returns the original value (which may include leading/trailing whitespace). Since the guard condition uses value.trim(), a value like " 176935... " would now be preserved (including spaces) and sent to the server, which is inconsistent with the number-parsing path and can break ID matching. Consider trimming once (e.g., const trimmed = value.trim()) and returning the trimmed string for this branch.

Suggested change
// Try to parse as number, but only if it's safe (won't lose precision)
const num = parseFloat(value);
if (!isNaN(num) && value.trim() === num.toString()) {
// Check if the number is within JavaScript's safe integer range
// Large integers (like UnixNano timestamps) lose precision as float64
if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {
// Keep as string to preserve precision
return value;
const trimmed = value.trim();
// Try to parse as number, but only if it's safe (won't lose precision)
const num = parseFloat(trimmed);
if (!isNaN(num) && trimmed === num.toString()) {
// Check if the number is within JavaScript's safe integer range
// Large integers (like UnixNano timestamps) lose precision as float64
if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {
// Keep as string (trimmed) to preserve precision while matching numeric parsing
return trimmed;

Copilot uses AI. Check for mistakes.
Comment on lines 518 to 527
// Try to parse as number, but only if it's safe (won't lose precision)
const num = parseFloat(value);
if (!isNaN(num) && value.trim() === num.toString()) {
// Check if the number is within JavaScript's safe integer range
// Large integers (like UnixNano timestamps) lose precision as float64
if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {
// Keep as string to preserve precision
return value;
}
return num;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces important new parsing behavior (integers > Number.MAX_SAFE_INTEGER remain strings) but there doesn’t appear to be a unit test covering it. Since Jest coverage is collected for livetemplate-client.ts, please add tests that assert: (1) values at/under MAX_SAFE_INTEGER parse to numbers, (2) values over it stay strings, and ideally (3) behavior with surrounding whitespace.

Copilot uses AI. Check for mistakes.
- Trim whitespace once at the start of parseValue for consistency
- Add comprehensive unit tests for large integer handling

Tests verify:
- Values at/under MAX_SAFE_INTEGER parse to numbers
- Values over MAX_SAFE_INTEGER stay as strings
- Whitespace handling is consistent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 25, 2026

Code Review

Overall: Good fix for the precision loss issue with large integers. The approach is sound and well-tested.

Issues Found

🔴 Critical Logic Bug (livetemplate-client.ts:526)
The condition trimmed === num.toString() will fail for large integers that you're trying to catch:

  • For "1769358878696557000", parseFloat() converts to 1769358878696557056 (precision loss)
  • Then 1769358878696557056 !== "1769358878696557000" is true
  • So it returns the string early (line 539) and never reaches your MAX_SAFE_INTEGER check (line 526)

The MAX_SAFE_INTEGER check is unreachable for values that need it. The logic should check the range before comparing string equality.

Suggested fix:

const num = parseFloat(trimmed);
if (!isNaN(num)) {
  if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {
    return trimmed;
  }
  if (trimmed === num.toString()) {
    return num;
  }
}

Minor Issues

  1. Line 532 comment: "to preserve precision while matching server expectations" - this is unclear about what server expectations are. Consider: "to preserve precision for server-side GetStringOk()"

  2. Test file line 108: Comment says "Boolean parsing uses exact match" but the test expects " true " to remain as-is (with spaces). This contradicts line 117 where "true" becomes boolean. Consider clarifying or fixing the test expectation.

Positive Notes

  • Comprehensive test coverage including edge cases
  • Clear problem statement and solution approach
  • Trimming optimization is good (only once)

The previous logic had a critical bug: the MAX_SAFE_INTEGER check was
inside a condition that already fails for large integers due to precision
loss during parseFloat(). For example:

- parseFloat("1769358878696557000") returns 1769358878696557056
- "1769358878696557000" === "1769358878696557056" is false
- So the outer if block never executed and MAX_SAFE_INTEGER was unreachable

The fix moves the range check BEFORE the string equality check, ensuring
large integers are correctly identified and preserved as strings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
adnaan added a commit to livetemplate/lvt that referenced this pull request Jan 26, 2026
Updates the embedded browser client to fix a critical bug where the
MAX_SAFE_INTEGER check was unreachable for large integers due to
precision loss during parseFloat().

See livetemplate/client#19 for details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 26, 2026

Review Summary

The fix correctly addresses precision loss for large integers. The logic is sound and well-tested.

Bug Found

Boolean parsing inconsistency (line 537-538): The boolean checks use the original value instead of trimmed, which is inconsistent with the numeric parsing path. This means strings like " true " won't parse as booleans even though they're trimmed for numeric checks.

Fix:

if (trimmed === "true") return true;
if (trimmed === "false") return false;

Code Quality

  • ✅ Logic correctly identifies integers > MAX_SAFE_INTEGER and preserves as strings
  • ✅ Comprehensive test coverage including edge cases
  • ✅ Clear comments explaining the precision issue
  • ✅ Proper use of Number.isInteger() and Math.abs()

The fix solves a real data corruption issue. Just fix the boolean trimming inconsistency and this is ready to merge.

@adnaan adnaan merged commit 8c13758 into main Jan 26, 2026
6 checks passed
@adnaan adnaan deleted the fix/large-integer-precision branch January 26, 2026 17:06
adnaan added a commit to livetemplate/lvt that referenced this pull request Jan 26, 2026
* chore: update embedded client with large integer fix

Updates the embedded client library to include the fix for preserving
large integers (like UnixNano timestamps) as strings to prevent
JavaScript precision loss.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: update embedded client with whitespace fix

Updates the embedded client to include the parseValue whitespace
trimming fix from the code review.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: update embedded client with MAX_SAFE_INTEGER logic fix

Updates the embedded browser client to fix a critical bug where the
MAX_SAFE_INTEGER check was unreachable for large integers due to
precision loss during parseFloat().

See livetemplate/client#19 for details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants